package collector

import (
	"encoding/json"
	"fmt"
	"github.com/prometheus/common/log"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"os"
	"strings"
	"time"
)

const (
	GetError    = -6.0
	Removed     = -5.0
	Degraded    = -4.0
	Inactive    = -3.0
	Activating  = -2.0
	StartedOnce = -1.0
	Active      = 0
	Upgraded    = 1.0
	Upgrading   = 2.0

	EventService = "service"
	EventHost    = "host"
)

type RancherConfig struct {
	RancherEnv []string `yaml:"rancher_env"`
	Url        string   `yaml:"rancher_url"`
	AccessKey  string   `yaml:"access_key"`
	SecretKey  string   `yaml:"secret_key"`
}
type RancherExporterConfig struct {
	RancherConf   []*RancherConfig `yaml:"rancher_config"`
	RedisHost     string           `yaml:"redis_host"`
	RedisPort     string           `yaml:"redis_port"`
	RedisDB       string           `yaml:"redis_db"`
	RedisPassword string           `yaml:"redis_password"`
}

const (
	RancherAccessKey = "RANCHER_ACCESS_KEY"
	RancherSecretKey = "RANCHER_SECRET_KEY"
	RancherUrl       = "RANCHER_URL"
	RancherEnv       = "RANCHER_ENV"

	RedisHost     = "REDIS_HOST"
	RedisPort     = "REDIS_PORT"
	RedisPassword = "REDIS_PASSWORD"
	RedisDB       = "REDIS_DB"
)

var (
	envList = []string{
		RancherAccessKey, RancherSecretKey, RancherUrl, RancherEnv, RedisHost, RedisPort, RedisDB,
	}

	RancherExporterConf = new(RancherExporterConfig)
)

func NewRancherExporter(configFile string) (err error) {
	// default read config file
	if configFile != "" {
		data, err := ioutil.ReadFile(configFile)
		if err != nil {
			log.Error(err)
			return err
		}
		err = yaml.Unmarshal(data, &RancherExporterConf)
		if err != nil {
			return err
		}
	} else {
		for _, env := range envList {
			if os.Getenv(env) == "" {
				panic(fmt.Sprintf("No Env %s", env))
			}
		}

		RancherExporterConf.RedisHost = os.Getenv(RedisHost)
		RancherExporterConf.RedisPort = os.Getenv(RedisPort)
		RancherExporterConf.RedisPassword = os.Getenv(RedisPassword)
		RancherExporterConf.RedisDB = os.Getenv(RedisDB)

		rancherConf := &RancherConfig{
			Url:        os.Getenv(RancherUrl),
			AccessKey:  os.Getenv(RancherAccessKey),
			SecretKey:  os.Getenv(RancherSecretKey),
			RancherEnv: strings.Split(os.Getenv(RancherEnv), ","),
		}
		RancherExporterConf.RancherConf = append(RancherExporterConf.RancherConf, rancherConf)
	}
	// init redis pool
	RedisPool = initRedisPool()

	return err
}

func StartSubscription(rc *RancherExporterConfig) {
	for _, r := range rc.RancherConf {
		for _, env := range r.RancherEnv {
			log.Infof("Subscribe rancher env: %s", env)
			rancherConfig := Config{
				AccessKey:  r.AccessKey,
				SecretKey:  r.SecretKey,
				URL:        r.Url,
				RancherEnv: env,
			}
			go StartRancherEvent(rancherConfig)
		}
	}
}

type RancherEvent struct {
	Service Service
	Host    Host
}

type Service struct {
	Id           string `json:"id"`
	State        string `json:"state"`
	HealthState  string `json:"healthState"`
	CombineState string
	RancherEnv   string
	Name         string `json:"name"`
	Uuid         string `json:"uuid"`
}

type Host struct {
	Id          string `json:"id"`
	State       string `json:"state"`
	HealthState string `json:"healthState"`
	AgentState  string `json:"agentState"`
	RancherEnv  string
	Uuid        string `json:"uuid"`
	HostName    string `json:"hostname"`
}

func (re *RancherEvent) Event(event *Event, conf Config) (err error) {
	if event.ResourceType == EventService {
		err = re.ServiceEvents(event, conf)
	} else if event.ResourceType == EventHost {
		err = re.HostEvents(event, conf)
	}
	return
}

func (re *RancherEvent) WriteToRedis(keyName string, valueBytes []byte, state string) (err error) {
	cli := RedisPool.Get()
	if cli.Err() != nil {
		log.Error(cli.Err())
		return err
	}
	defer cli.Close()

	if state == "removed" {
		time.Sleep(time.Second)
		_, err = cli.Do("Del", keyName)
		if err != nil {
			log.Errorf("redis Set failed:", err)
			return err
		}
	} else {
		_, err = cli.Do("SET", keyName, valueBytes)
		if err != nil {
			log.Errorf("redis Set failed:", err)
			return err
		}
	}

	return nil
}

// host
func (re *RancherEvent) HostEvents(event *Event, c Config) (err error) {
	resource, _ := event.Data["resource"].(map[string]interface{})
	resourceByte, _ := json.Marshal(resource)
	err = json.Unmarshal(resourceByte, &re.Host)
	if err != nil {
		log.Error(err)
		return err
	}
	re.Host.RancherEnv = c.RancherEnv
	hostByte, err := json.Marshal(re.Host)
	if err != nil {
		log.Error(err)
		return
	}

	err = re.WriteToRedis(fmt.Sprintf("host_%s", re.Host.Id), hostByte, re.Host.State)
	if err == nil {
		log.Infof("%s %s %s %s set OK", re.Host.RancherEnv, re.Host.Id, re.Host.HostName, re.Host.Uuid)
	}
	return nil
}

// service
func (re *RancherEvent) ServiceEvents(event *Event, c Config) (err error) {
	resource, _ := event.Data["resource"].(map[string]interface{})
	resourceByte, _ := json.Marshal(resource)

	err = json.Unmarshal(resourceByte, &re.Service)
	if err != nil {
		log.Error(err)
		return err
	}
	re.Service.RancherEnv = c.RancherEnv
	re.Service.CombineState = re.Service.State
	if re.Service.State == "active" && re.Service.HealthState != "healthy" {
		re.Service.CombineState = re.Service.HealthState
	}

	serviceByte, _ := json.Marshal(re.Service)
	if err != nil {
		log.Error(err)
		return err
	}

	err = re.WriteToRedis(fmt.Sprintf("service_%s", re.Service.Id), serviceByte, re.Service.State)
	if err == nil {
		log.Infof("%s %s %s set OK",re.Service.RancherEnv, re.Service.Name, re.Service.Uuid)
	}
	return
}
